﻿using System;
using System.Linq.Expressions;

namespace Kiva7.Utilities
{
    /// <summary> 
    /// Provides the <see cref="Of"/> extension method that works as a workarounds for a nameof() operator,  
    /// which should be added to C# sometime in the future. 
    /// </summary> 
    public static class Name
    {
        public static string Of<TClass>()
        {
            return typeof(TClass).Name;
        }

        /// <summary> 
        /// Returns a string representation of a property name (or a method name), which is given using a lambda expression. 
        /// </summary> 
        /// <typeparam name="TClass">The type of the <paramref name="obj"/> parameter.</typeparam> 
        /// <typeparam name="TProperty">The type of the property (or the method's return type), which is used in the <paramref name="propertyExpression"/> parameter.</typeparam> 
        /// <param name="obj">An object, that has the property (or method), which its name is returned.</param> 
        /// <param name="propertyExpression">A Lambda expression of this pattern: x => x.Property <BR/> 
        /// Where the x is the <paramref name="obj"/> and the Property is the property symbol of x.<BR/> 
        /// (For a method, use: x => x.Method()</param> 
        /// <returns>A string that has the name of the given property (or method).</returns> 
        public static string NameOf<TClass, TProperty>(this TClass obj, Expression<Func<TClass, TProperty>> propertyExpression)
        {
            return Of(propertyExpression.Body);
        }

        /// <summary> 
        /// Returns a string representation of a property name (or a method name), which is given using a lambda expression. 
        /// </summary> 
        /// <example>
        /// Name.Of@lt;string$gt;(x => x.Length)        
        /// </example>
        /// <typeparam name="TClass">The type of the class parameter.</typeparam> 
        /// <param name="propertyExpression">A Lambda expression of this pattern: x => x.Property <BR/> 
        /// (For a method, use: x => x.Method()</param> 
        /// <returns>A string that has the name of the given property (or method).</returns> 
        public static string Of<TClass>(Expression<Func<TClass, object>> propertyExpression)
        {
            return Of(propertyExpression.Body);
        }

        /// <summary> 
        /// Returns a string representation of a property name (or a method name), which is given using a lambda expression. 
        /// </summary> 
        /// <example>
        /// Name.Of@lt;string, int$gt;(x => x.Length)        
        /// </example>
        /// <typeparam name="TClass">The type of the class parameter.</typeparam> 
        /// <typeparam name="TProperty">The type of the property (or the method's return type), which is used in the <paramref name="propertyExpression"/> parameter.</typeparam> 
        /// <param name="propertyExpression">A Lambda expression of this pattern: x => x.Property <BR/> 
        /// (For a method, use: x => x.Method()</param> 
        /// <returns>A string that has the name of the given property (or method).</returns> 
        public static string Of<TClass, TProperty>(Expression<Func<TClass, TProperty>> propertyExpression)
        {
            return Of(propertyExpression.Body);
        }

        /// <summary> 
        /// Returns a string representation of a property name (or a method name), which is given using a lambda expression. 
        /// </summary> 
        /// <typeparam name="TProperty">The type of the property (or the method's return type), which is used in the <paramref name="propertyExpression"/> parameter.</typeparam> 
        /// <param name="propertyExpression">A Lambda expression of this pattern: () => x.Property <BR/> 
        /// Where Property is the property symbol of x.<BR/> 
        /// (For a method, use: () => x.Method()</param> 
        /// <returns>A string that has the name of the given property (or method).</returns> 
        public static string OfStatic<TProperty>(Expression<Func<TProperty>> propertyExpression)
        {
            return Of(propertyExpression.Body);
        }

        private static string Of(object propertyExpressionBody)
        {
            UnaryExpression unaryExp = propertyExpressionBody as UnaryExpression;
            if (unaryExp != null)
                return Of(unaryExp.Operand);

            MemberExpression memberExp = propertyExpressionBody as MemberExpression;
            if (memberExp != null)
                return memberExp.Member.Name;

            MethodCallExpression methodExp = propertyExpressionBody as MethodCallExpression;
            if (methodExp != null)
                return methodExp.Method.Name;

            throw new ArgumentException("'expression' should be a member expression or a method call expression.", "expression");

        }

        //static class Program
        //{
        //static void Main()
        //{

        //string n2 = Name.Of<string, int>(x => x.Length);

        //        string n = Name.Of(() => default(Test).TestProperty);

        //        Console.WriteLine(Name.Of(() => default(Test).GetType())); //gets the name of an object's method. 

        //        Console.WriteLine(Name.Of(() => string.Empty)); //gets the name of a class' property. 
        //        Console.WriteLine(Name.Of(() => string.Copy(""))); //gets the name of a class' method. 
        //    }
        //}
        //}
    }
}